package ldh.im.rpc.connection;

import ldh.im.rpc.Rpc;
import ldh.im.rpc.RpcMapper;
import ldh.im.rpc.config.RpcConfig;
import ldh.im.rpc.dto.RpcData;
import ldh.im.rpc.exception.RpcErrorCode;
import ldh.im.rpc.exception.RpcException;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

public abstract class RpcServiceProxy implements InvocationHandler {

    protected RpcConfig rpcConfig;

    public RpcServiceProxy(Class rpcServiceClazz, RpcConfig rpcConfig) {
        if (rpcServiceClazz == null) {
            throw new NullPointerException("rpcServiceClazz must not null");
        }
        Rpc rpc = (Rpc) rpcServiceClazz.getAnnotation(Rpc.class);
        if (rpc == null) {
            throw new IllegalArgumentException(rpcServiceClazz + " is not rpc service");
        }
        this.rpcConfig = rpcConfig;
        check(rpcServiceClazz);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcMapper rpcMapper = method.getAnnotation(RpcMapper.class);
        String path = rpcMapper.path();
        RpcData rpcData = new RpcData();
        rpcData.setPath(path);
        rpcData.setVersion(1);
        rpcData.setStatus(0);

        int readTimeout = rpcMapper.readTimeout();
        return write(rpcData, readTimeout, args);
    }

    public abstract Object write(RpcData rpcData, int readTimeout, Object[] args) throws InterruptedException, ExecutionException, TimeoutException;

    protected RpcCallback getRpcCallback(Object[] args) {
        if (args == null) return null;
        for (Object obj : args) {
            if (obj instanceof RpcCallback) {
                return (RpcCallback)obj;
            }
        }
        return null;
    }

    private Object getRpcChannelParam(Class[] paramClasses, Object[] args) {
        for (Object arg : args) {
            if (arg instanceof RpcReadChannel) {
                return arg;
            }
        }
        throw new IllegalArgumentException("RpcProxy代理传输参数必须有一个Channel对象");
    }

    private void check(Class rpcServiceClazz) {
        Method[] methods = rpcServiceClazz.getDeclaredMethods();
        for (Method method : methods) {
            RpcMapper rpcMapper = method.getAnnotation(RpcMapper.class);
            if (rpcMapper == null) {
                throw new RpcException(RpcErrorCode.Api_error, method.getName());
            }
            checkAsynchronous(rpcMapper, method, method.getParameterTypes());
        }
    }

    private void checkAsynchronous(RpcMapper rpcMapper, Method method, Class<?>[] paraClasses) {
        int total = 0;
        boolean isRight = false;
        for (Class clazz : paraClasses) {
            if (clazz == RpcCallback.class) {
                total++;
                if (method.getReturnType() == void.class) {
                    isRight = true;
                }
            }
        }
        if (total == 1 && isRight) return;
        throw new RpcException(RpcErrorCode.Api_error, method.toGenericString());
    }
}
